package graph;

import java.util.LinkedList;


import bookworm.AdministratorOfAllBookTagsUsedByTheSystem;
import bookworm.AdministratorOfAllReviewersNames;
import bookworm.ParallelThreadsCounter;
import bookworm.Review;

/**
 * this class will have common behavior between all BookReviewGraphConstructors.
 * The only abstract method is "getBookReviewsFromOneTag". It's the only function with
 * connection to revish API or to text file.
 * @author Mina
 *
 */
public abstract class AbstractBookReviewGraphConstructor 
{
	private BookReviewGraphInterface graph;
	
	/**
	 * we'll populate the graph with nodes based on all book reviews from all tags
	 * of our system
	 */
	protected LinkedList<Review> bookReviewsFromAllBookTags; 
	
	public AbstractBookReviewGraphConstructor()
	{
		bookReviewsFromAllBookTags = new LinkedList<Review>();
	}
	
	public BookReviewGraphInterface getGraph()
	{
		return graph;
	}
	
	public int gethowManyBookReviewsAreOnTheSystem()
	{
		return bookReviewsFromAllBookTags.size();
	}
	
	
	/**
	 * To create the graph, we'll get reviews from all book tags and create new nodes for
	 * each Review. Then we'll add new edges to connect the new node to other 
	 * "already on the graph" nodes. One edge will be added only if the new node
	 * has either a tag or an author in common with an old node.
	 */
	public BookReviewGraphInterface createGraph()
	{
		graph = new BookReviewGraph();
		retrieveBookReviewsFromAllBookTags();
		
		
		for(int i = 0; i < this.bookReviewsFromAllBookTags.size(); i++)
		{
			Review oneReview = bookReviewsFromAllBookTags.get(i);
			BookReviewNode newNode = this.createNewNodeForGraph(oneReview);
			this.createNewIncidentEdgesForNewNodeBasedOnTags(newNode);
			this.createNewIncidentEdgesForNewNodeBasedOnAuthors(newNode);
		}
		
		/*program won't continue unless all the created Threads
		  (the ones that add Edges to each newNode showed above) finish their work.
		  So that means the program won't continue unless all book reviews 
	      from the tags we use are on the graph
	     */
		ParallelThreadsCounter countsActiveThreads = 
				ParallelThreadsCounter.getParallelThreadsCounter();
		int howManyThreadsAreActive = countsActiveThreads.getHowManyThreadsAreActive();
		while(howManyThreadsAreActive > 0)
		{
			//creator of graph will only end this function when graph is created
			howManyThreadsAreActive = countsActiveThreads.getHowManyThreadsAreActive();
		}
		
		return this.graph;
	}
	
	protected void retrieveBookReviewsFromAllBookTags()
	{
		LinkedList<String> allBookTags = 
				AdministratorOfAllBookTagsUsedByTheSystem.getAllBookTags();
		for(int i = 0; i < allBookTags.size(); i++)
		{
			String tag = allBookTags.get(i);
			LinkedList<Review> reviewsFromOneTag = this.getBookReviewsFromOneTag(tag);
			for(int j = 0; j < reviewsFromOneTag.size(); j++)
			{
				Review oneReviewOfReviewsFromOneTag = reviewsFromOneTag.get(j);
				if(isReviewAlreadyInBookReviewsFromAllBookTags(oneReviewOfReviewsFromOneTag) == false)
				{
					//review must be added to LinkedList called "bookReviewsFromAllBookTags"
					this.bookReviewsFromAllBookTags.add(oneReviewOfReviewsFromOneTag);
				
				}
			}
		}
	}
	
	
	/**
	 * This function will be different if the constructor uses the API or the text file,
	 * so, it is a template method
	 * @param tag
	 * @return
	 */
	public abstract LinkedList<Review> getBookReviewsFromOneTag(String tag);
	
	
	/**
	 * Checks if a review is already on the LinkedList called "bookReviewsFromAllBookTags"
	 * @param tag
	 * @return
	 */
	protected boolean isReviewAlreadyInBookReviewsFromAllBookTags(Review review)
	{
		int reviewId = review.getReviewId();
		for(int i = 0; i < this.bookReviewsFromAllBookTags.size(); i++)
		{
			Review oneReviewOfBookReviewsFromAllBookTags = bookReviewsFromAllBookTags.get(i);
			if(reviewId == oneReviewOfBookReviewsFromAllBookTags.getReviewId())
			{
				//the review is already on LinkedList
				return true;
			}
		}
		return false;
	}
	
	private BookReviewNode createNewNodeForGraph(Review reviewData)
	{
		BookReviewNode newNode = graph.createNewNode(reviewData);
		
		//let's add the reviewer name in the list of the administrator of reviewer names
		String reviewerName = reviewData.getReviewer();
		AdministratorOfAllReviewersNames knowsReviewersnames =
									AdministratorOfAllReviewersNames.getInstance();
		knowsReviewersnames.addNewReviewerName(reviewerName);
		
		//and check if the new node can be a starting node...
		this.checkIfNewNodeCanBeAStartingNode(newNode);
		
		return newNode;
	}
	
	/**
	 * This function will create new starting nodes for the graph if needed based
	 * on the authors and the tags from the new node.
	 * "Starting nodes" will be the first node on the graph with a specific author or tag.
	 * These starting nodes will be useful when we want to traverse the graph because
	 * they give us a starting point.
	 * @param newNode
	 */
	private void checkIfNewNodeCanBeAStartingNode(BookReviewNode newNode)
	{
		AdministratorOfAllStartingNodes administrator = 
				AdministratorOfAllStartingNodes.getAdministratorOfAllStartingNodes();
		
		//let's see if newNode can be a starting node for one of his tags
		LinkedList<String> tagsFromNode = newNode.getReviewData().getTags();
		for(int i = 0; i < tagsFromNode.size(); i++)
		{
			String oneTag = tagsFromNode.get(i);
			BookReviewNode startingNode= administrator.getStartingNodeForTag(oneTag);
			if(startingNode == null)
			{
				//the "newNode" must also be a new starting node
				administrator.addNewStartingNodeForTags(newNode);
			}
		}
		
		//let's see if newNode can be a starting node for one of his authors
		LinkedList<String> authorsFromNode = newNode.getReviewData().getAuthors();
		for(int j = 0; j < authorsFromNode.size(); j++)
		{
			String oneAuthor = authorsFromNode.get(j);
			BookReviewNode startingNode = administrator.getStartingNodeForAuthor(oneAuthor);
			if(startingNode == null)
			{
				administrator.addNewStartingNodeForAuthors(newNode);
			}
		}
	}
	
	/**
	 * Function below will add new Edges from newNode to other nodes with same tags using Threads
	 * Also, when a thread is created, the singleton class that holds the number of threads
	 * in execution will increment its "counter" by 1.
	 * @param newNode
	 */
	private void createNewIncidentEdgesForNewNodeBasedOnTags(BookReviewNode newNode)
	{
		ThreadCreateEdgesOfGraphBasedOnTags thread = 
				new ThreadCreateEdgesOfGraphBasedOnTags(newNode, this.graph);
		ParallelThreadsCounter countsActiveThreads = 
				ParallelThreadsCounter.getParallelThreadsCounter();
		countsActiveThreads.increaseCounterThreadsActive();
		thread.start();
	}
	
	
	/**
	 * this function will do the same as "createNewIncidentEdgesForNewNodeBasedOnTags", but
	 * from authors.
	 * @param newNode
	 */
	private void createNewIncidentEdgesForNewNodeBasedOnAuthors(BookReviewNode newNode)
	{
		ThreadCreateEdgesOfGraphBasedOnAuthors thread = 
				new ThreadCreateEdgesOfGraphBasedOnAuthors(newNode, this.graph);
		ParallelThreadsCounter countsActiveThreads = 
				ParallelThreadsCounter.getParallelThreadsCounter();
		countsActiveThreads.increaseCounterThreadsActive();
		thread.start();
	}
}
